/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.booter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.felix.framework.Felix;
import org.apache.felix.framework.cache.BundleCache;
import org.apache.felix.framework.util.StringMap;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;

/**
 * This class is the entry point to the JDPF framework.
 * <p>
 * The class is used to configure and boot the OSGi framework.<br>
 * To configure the Osgi framework you can use two different strategies
 * separately or mixed together:
 * <ul>
 * <li>use the configuration file <code>config.properites</code> you find in
 * the <code>conf</code> directory</li>
 * <li>configure some properties hard coding them in this class and passing
 * them to the framework at startup time.</li>
 * </ul>
 * Usually we use both strategy mixed together: the first one to configure
 * general properties that can fit to all systems, such as the packages the
 * System Bundle has to export (this should be all Java packages that don't
 * match the pattern java.*), while we use the second one to set those
 * properties that usually are system dependent such as the <code>cache</code>
 * directory or the bundles we want to start together with the framework.<br>
 * After the OSGi framework is up, the class installs all custom bundles it
 * finds in the <code>bundle</code> directory.
 * </p>
 * 
 * @author Bruno Farina
 */
public class FrameworkApplication {
	
	private static final String[] coreBundles={"org.jdpf.core","org.jdpf.core.paramloader",
		"org.jdpf.core.netloader","org.jdpf.core.contextmanager","org.jdpf.plugins.graphics.netloadergui"};

	/**
	 * The bundle activator class used to get access to the bundle context of
	 * the System Bundle.
	 */
	private ApplicationActivator activator;

	/**
	 * The bundle context of the System Bundle.
	 */
	private BundleContext context;

	/**
	 * The Apache OSGi framework implementation
	 */
	private Felix felix;

	/**
	 * Return an istanc of the JDPF framework with a running instance of the
	 * OSGi framework.
	 * @throws IOException 
	 * @throws IOException 
	 */
	public FrameworkApplication() throws IOException{
		final File cachedir = File.createTempFile("org.jdpf.cache", null);
		cachedir.delete();
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				deleteFileOrDir(cachedir);
			}
		});

		Properties prop = new Properties();
		try {
			InputStream configStream=this.getClass().getClassLoader().getResourceAsStream("config.properties");
			prop.load(configStream);
		} catch (FileNotFoundException e) {
			System.out.println("[FATAL] Unable to find the properties file 'config.properties'.\n" +
					"System will shutdown immediatelly.");
		} catch (IOException e) {
			System.out.println("[FATAL] Unable to read the properties file 'config.properties'.\n" +
					"System will shutdown immediatelly.");
		}

		prop.put(BundleCache.CACHE_PROFILE_DIR_PROP, cachedir.getAbsolutePath());

		activator = new ApplicationActivator();
		List<BundleActivator> activators = new ArrayList<BundleActivator>(5);
		activators.add(activator);

		felix = new Felix(new StringMap(prop, false), activators);
		try {
			felix.start();
		} catch (BundleException e) {
			System.out.println(e.getMessage());
		}

		context = activator.getContext();
		
		installShell();
		installCoreBundles();
		installBundles();

		startBundle(coreBundles[0]);
		startBundle(coreBundles[1]);
		startBundle(coreBundles[2]);
		startBundle(coreBundles[3]);
		startBundle(coreBundles[4]);
		
		startAllBlockBundles();
	}

	private void installShell() {
		String[] shells={"org.apache.felix.bundlerepository-0.8.0-incubator.jar",
				"org.apache.felix.shell-0.8.0-incubator.jar","org.apache.felix.shell.tui-0.8.0-incubator.jar"};
		for(int i=0;i<shells.length;i++){
			InputStream bundleStream=this.getClass().getClassLoader().getResourceAsStream(shells[i]);
			try {
				context.installBundle(shells[i],bundleStream);
				System.out.println("[INFO] Bundle " + shells[i]
				                  		+ " installed successfully.");
			} catch (BundleException e) {
				System.out.println("[ERROR] Unable to install shell bundle "+shells[i]+" due to:\n"+e.getMessage());
			}
		}
	}

	private void startAllBlockBundles() {
		Bundle[] bundles=context.getBundles();
		for(int i=0;i<bundles.length;i++){
			if(!(bundles[i].getState()==Bundle.ACTIVE)){
				try {
					bundles[i].start();
				} catch (BundleException e) {
					System.out.println("[ERROR] Unable to start bundle "+bundles[i].getSymbolicName()
							+" due to:\n"+e.getMessage());
				}
			}
		}
		
	}

	private void installCoreBundles() {
		String root="./../";
		for(int i=0;i<coreBundles.length;i++){
			File bundleFolder=new File(root+coreBundles[i]+"/target");
			File[] files=bundleFolder.listFiles();
			for(int j=0;j<files.length;j++){
				if(files[j].getName().startsWith(coreBundles[i]))
					try {
						context.installBundle("file:"+files[j].getAbsolutePath());
						System.out.println("[INFO] Bundle " + coreBundles[j]
								+ " installed successfully.");
					} catch (BundleException e) {
						System.out.println("[FATAL] Unable to install the core bundle "+coreBundles[i]+" due to:\n"+e.getMessage());
						System.exit(-1);
					}
			}
		}
	}

	private void startBundle(String id) {
		Bundle[] bundles = context.getBundles();
		boolean flag = true;
		for (int i = 0; i < bundles.length && flag; i++) {
			if (id.equals(bundles[i].getSymbolicName())){
				try {
					bundles[i].start();
					System.out.println("[INFO] Bundle "+id+" started successfully.");
				} catch (BundleException e) {
					System.out.println("[FATAL] Unable to start bundle "+id+" due to:\n"+e.getMessage());
					System.exit(-1);
				}
				flag = false;
			}
		}
	}

	private void installBundles() {
		File root=new File("./../");
		File[] bundleFiles=root.listFiles();
		Bundle[] bundles = context.getBundles();
		for (int i = 0; i < bundleFiles.length; i++) {
			if(bundleFiles[i].getName()!=coreBundles[4]&&
					(bundleFiles[i].getName().startsWith("org.jdpf.plugins.")||
							bundleFiles[i].getName().startsWith("jscience"))){
				File bundleFile=new File(bundleFiles[i].getAbsoluteFile()+"/target");
				if(bundleFile.exists()){
					File[] content=bundleFile.listFiles();
					boolean bundleFound=false;
					for(int j=0;j<content.length;j++){
						if(content[j].getName().endsWith(".jar")&&
								!checkFor(bundles, bundleFiles[i])&&!content[j].isDirectory()){
							try {
								context.installBundle("file:"+ content[j].getAbsolutePath());
								bundleFound=true;
							} catch (BundleException e) {
								System.out.println("[ERROR] Unable to install bundle "+content[j].getName()+" due to:\n"+
										e.getMessage());
							}
							System.out.println("[INFO] Bundle " + content[j].getName()
									+ " installed successfully.");
						}
					}
					if(!bundleFound)
						System.out.println("[WARNING] Unable to find a bundle jar file for the project "+bundleFiles[i]+
						". No bundles installed");
				}else{
					System.out.println("[WARNING] Unable to find the target folder. No bundles from project "+bundleFiles[i]+
							" will be installed.");
				}
				
			}
		}
	}

	/**
	 * Check if a bundle is already present in a bundles array
	 * 
	 * @param bundles
	 *            the array of bundles already installed
	 * @param bundleFile
	 *            the file of the new bundle
	 * @return
	 *            <ul>
	 *            <li><code>true</code> if a bundle located in the file
	 *            <code>bundleFile</code> is already installed in the
	 *            framework</li>
	 *            <li><code>false</code> otherwise.</li>
	 *            </ul>
	 */
	private boolean checkFor(Bundle[] bundles, File bundleFile) {
		boolean flag = false;
		String file = "file:" + bundleFile.getName();
		for (int i = 0; i < bundles.length && !flag; i++) {
			String location = bundles[i].getLocation();
			flag = file.equals(location);
		}
		return flag;
	}
	
	private void deleteFileOrDir(File file) {
		if (file.isDirectory()) {
			File[] childs = file.listFiles();
			for (int i = 0; i < childs.length; i++) {
				deleteFileOrDir(childs[i]);
			}
		}
		file.delete();

	}
}
